﻿using CSharpTest.Dameng;
using NModbus;
using static Program;

partial class Program
{
    static async Task ModbusMasterTest(IServiceProvider serviceProvider)
    {
        var logger = serviceProvider.GetRequiredService<ILogger<Program>>();
        //var tcpClient = new TcpClient("127.0.0.1", 502);
        var tcpClient = new TcpClient("tp-e14-guo", 502);
        var fac = new NModbus.ModbusFactory();
        var master = fac.CreateMaster(tcpClient);

        for (int i = 0; i < 100; i++)
        {
            master = fac.CreateMaster(tcpClient);
            logger.LogInformation("******执行前连接状态：{Value}", tcpClient.Connected);
            if (!tcpClient.Connected)
            {
                tcpClient = new TcpClient("tp-e14-guo", 502);
            }
            try
            {
                var a = await master.ReadCoilsAsync(1, 0, 8);
                logger.LogInformation("线圈：{Value}", a);
                var b = await master.ReadInputsAsync(1, 0, 8);
                logger.LogInformation("离散输入：{Value}", b);
                var c = await master.ReadHoldingRegistersAsync(1, 0, 2);
                logger.LogInformation("保持寄存器：{Value}", c);
                var d = await master.ReadInputRegistersAsync(1, 0, 2);
                logger.LogInformation("输入寄存器：{Value}", d);
            }
            catch (Exception e)
            {
                logger.LogInformation(e, "异常");
                logger.LogInformation("@@@@@@@异常后连接状态：{Value}", tcpClient.Connected);
            }
            await Task.Delay(2000);
        }


        //slaveAddress是站号
        var data = await master.ReadHoldingRegistersAsync(1, 100, 2);
        //字节顺序是CDAB，其中一个字母表示一个字节，CD是一个short数字，AB是一个short数字
        //对一个short数值的两个字节的编码是大端模式高位在前，而多个数值是高地址在前相当于顺序是倒序的
        var byteArr = data.SelectMany(v => BitConverter.GetBytes(v)).ToArray();
        var num = BitConverter.ToInt32(byteArr);

    }

    static async Task ModbusSlaveTest()
    {
        var tcpListener = new TcpListener(IPAddress.Any, 502);
        var fac = new NModbus.ModbusFactory();
        var slaveNetwork = fac.CreateSlaveNetwork(tcpListener);
        var slave = fac.CreateSlave(1);
        var num = 123456789;
        var byteArr = BitConverter.GetBytes(num).ToArray();
        var data = byteArr.Select((v, i) => new { key = i / 2, value = v })
            .GroupBy(m => m.key, m => m.value, (k, v) => BitConverter.ToUInt16(v.ToArray())).ToArray();
        slaveNetwork.AddSlave(slave);
        slave.DataStore.InputRegisters.WritePoints(200, data);
        await slaveNetwork.ListenAsync();
    }

    static async Task ModbusServiceTest(IServiceProvider serviceProvider)
    {

        var service = serviceProvider.GetRequiredService<ModbusService>();
        var logger = serviceProvider.GetRequiredService<ILogger<Program>>();
        var timer = new System.Timers.Timer(2000);
        timer.Elapsed += async (s, e) =>
        {
            try
            {
                var a = await service.GetAllSwitchStatus(ModbusPositionBlock.Coil);
                logger.LogInformation("线圈状态：{@a}", a);
            }
            catch (Exception ex)
            {
                logger.LogError(ex, "获取线圈状态异常");
                //logger.LogError(ex, "获取线圈状态异常，调用连接重置");
                //service.Reset();
            }
        };
        timer.Start();
        await Task.Delay(100000);
        timer.Stop();
    }

    internal class ModbusService(ILogger<Program.ModbusService> logger, string host, int port)
    {
        // 连接的网络继电器的主机地址
        readonly string host = host;
        // 连接的网络继电器的端口
        readonly int port = port;
        private readonly ILogger<ModbusService> logger = logger;
        int resetRequestCount = 0;
        int resetCount = 0;
        /// <summary>
        /// 获取所有开关状态
        /// </summary>
        /// <param name="block">寄存器类型</param>
        /// <returns></returns>
        /// <exception cref="ArgumentOutOfRangeException">不可识别的寄存器类型</exception>
        internal async Task<bool[]> GetAllSwitchStatus(ModbusPositionBlock block)
        {
            var master = GetConnection();
            ushort startAddress = 0;
            ushort numberOfPoints = 8;
            //slaveAddress是站号
            return block switch
            {
                ModbusPositionBlock.Coil => await master.ReadCoilsAsync(1, startAddress, numberOfPoints),
                ModbusPositionBlock.Input => await master.ReadInputsAsync(1, startAddress, numberOfPoints),
                _ => throw new ArgumentOutOfRangeException(nameof(block), block, null),
            };
        }
        /// <summary>
        /// 更改指定地址的开关状态
        /// </summary>
        /// <param name="address">Modbus地址</param>
        /// <param name="isOn">闭合为true，断开为false</param>
        /// <returns></returns>
        internal async Task ChangeSwitchStatus(ushort address, bool isOn)
        {
            var master = GetConnection();
            await master.WriteSingleCoilAsync(1, address, isOn);
        }
        /// <summary>
        /// 关闭所有输出开关
        /// </summary>
        /// <returns></returns>
        internal async Task TurnOffAllWitch()
        {
            var master = GetConnection();
            await master.WriteMultipleCoilsAsync(1, 0, new bool[8]);
        }
        TcpClient connection = new(host, port);
        readonly ModbusFactory modbusFac = new();
        private static readonly Lock lockGetConn = new();
        IModbusMaster GetConnection()
        {
            if (!connection.Connected)
            {
                lock (lockGetConn)
                {
                    //已断开，关闭创建新的
                    if (!connection.Connected)
                    {
                        logger.LogError("TCP连接已断正在重连！");
                        connection.Close();
                        connection = new TcpClient(host, port);
                        resetRequestCount = 0;
                        resetCount = 0;
                    }
                }
            }
            return modbusFac.CreateMaster(connection);
        }
        private static readonly Lock lockReset = new();
        /// <summary>
        /// 强制重置连接
        /// </summary>
        internal void Reset()
        {
            lock (lockReset)
            {
                if (resetCount > 2)
                {
                    logger.LogCritical("Modbus连接重置次数达到阈值，执行应用关闭");
                    throw new InvalidOperationException("Modbus连接重置次数达到阈值，执行应用关闭");
                }
                if (resetRequestCount > 10)
                {
                    resetRequestCount = 0;
                    logger.LogInformation("Modbus连接重置请求次数已达到阈值，执行重置");
                    connection.Close();
                    resetCount++;
                    return;
                }
                logger.LogWarning("Modbus连接请求重置，累计请求次数：{Count}", resetRequestCount);
                resetRequestCount++;
            }
        }
    }

    internal enum ModbusPositionBlock
    {
        /// <summary>
        /// 线圈状态
        /// </summary>
        Coil,
        /// <summary>
        /// 离散状态
        /// </summary>
        Input,
        /// <summary>
        /// 保持寄存器
        /// </summary>
        HoldingRegister,
        /// <summary>
        /// 输入寄存器
        /// </summary>
        InputRegister
    }

}
namespace CSharpTest
{
    public static class ModbusServiceCollectionExtensions
    {
        public static void RegisterModbusService(this IServiceCollection services)
        {
            services.AddSingleton(sp =>
            {
                var logger = sp.GetRequiredService<ILogger<Program.ModbusService>>();
                return new ModbusService(logger, "tp-e14-guo", 502);
            });
        }
    }
}
